//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <dma.h>
#include "8237a.h"

//
// class DmaController
//
class DmaController : public CDeviceDriver
{
public:
    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    virtual void Dispose();

public:
    DmaController();

    ECode RequestDmaChannel(uint_t uNo, DzDmaChannel **ppDmaChannel);
    void ReleaseDmaChannel(DzDmaChannel *pDmaChannel);

public:
    DzDmaChannel  m_dmaChannels[c_uNumberOfDmaChannels];
};

STATIC DmaController *s_pDmaController = NULL;

ECode DmaController::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode DmaController::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode DmaController::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

void DmaController::Dispose()
{
    s_pDmaController = NULL;
    delete this;
}

DmaController::DmaController()
{
    for (uint_t i = 0; i < c_uNumberOfDmaChannels; i++) {
        m_dmaChannels[i].m_bBusy = FALSE;
    }

    m_dmaChannels[4].m_bBusy = TRUE;
}

ECode DmaController::RequestDmaChannel(uint_t uNo,
                DzDmaChannel **ppDmaChannel)
{
    if (c_uNumberOfDmaChannels <= uNo ||
        NULL == ppDmaChannel) {
        return E_INVALID_ARGUMENT;
    }

    if (InterlockedExchange(&m_dmaChannels[uNo].m_bBusy, TRUE)) {
        return E_DRIVER_BUSY;
    }

    AddRef();

    *ppDmaChannel = &m_dmaChannels[uNo];
    return NOERROR;
}

void DmaController::ReleaseDmaChannel(DzDmaChannel *pDmaChannel)
{
    assert(pDmaChannel);
    assert(pDmaChannel->m_bBusy);

    pDmaChannel->m_bBusy = FALSE;
    Release();
}

EXTERN IDeviceDriver * CDECL CreateDmaController(uint_t uDeviceNo,
                void *pvParameter)
{
    assert(NULL == s_pDmaController);

    s_pDmaController = new DmaController();
    if (NULL == s_pDmaController) return NULL;

    s_pDmaController->AddRef();
    return s_pDmaController;
}

//
// class DmaChannel
//
ECode DzDmaChannel::RequestTransfer(
    DmaTransferMode mode, virtaddr_t vaBuffer, uint32_t uLength)
{
    assert(s_pDmaController);
    register uint_t uNo = this - s_pDmaController->m_dmaChannels;
    assert(c_uNumberOfDmaChannels > uNo);

    if (!IsDmaModeAvailable(mode)) return E_FAIL;

    bool_t bOriginalPreemption = DzDisablePreemption();

    DisableDmaChannel(uNo);

    ClearDmaBytePointer(uNo);

    SetDmaMode(uNo, mode);
    SetDmaChannelBaseAddress(uNo, VIRTADDR_TO_BUSADDR(vaBuffer));
    SetDmaChannelTransferCount(uNo, uLength);

    EnableDmaChannel(uNo);

    DzRestorePreemption(bOriginalPreemption);

    return NOERROR;
}

ECode DzDmaChannel::RequestTransfer(
    virtaddr_t vaDestBuffer, virtaddr_t vaSrcBuffer, uint32_t uLength)
{
    return E_NOT_IMPLEMENTED;
}

uint32_t DzDmaChannel::GetTransferResidue()
{
    assert(s_pDmaController);
    register uint_t uNo = this - s_pDmaController->m_dmaChannels;
    assert(c_uNumberOfDmaChannels > uNo);

    bool_t bOriginalPreemption = DzDisablePreemption();

    uint32_t count = GetDmaTransferCount(uNo);

    DzRestorePreemption(bOriginalPreemption);

    return count;
}

void DzDmaChannel::Release()
{
    assert(s_pDmaController);
    s_pDmaController->ReleaseDmaChannel(this);
}

EXTERN ECode DzRequestDmaChannel(uint_t uNo, DzDmaChannel **ppDmaChannel)
{
    *ppDmaChannel = NULL;

    if (NULL == s_pDmaController) return E_DMAC_NOT_FOUND;

    return s_pDmaController->RequestDmaChannel(uNo, ppDmaChannel);
}
